package de.mmis.core.publishsubscribe.filter;

import java.util.HashMap;
import java.util.Map;

import de.mmis.core.base.DeserializableAnnotations.DeserializationMethod;
import de.mmis.core.base.abstracttree.InnerNode;
import de.mmis.core.base.abstracttree.Tree;
import de.mmis.core.base.abstracttree.serializer.DeserializableException;

public class CompoundFilter extends Filter {

	private final BooleanOperator op;

	private Filter filter1, filter2;

	public CompoundFilter(Filter f) {
		this.op = null;
		this.filter1 = f;
	}

	public CompoundFilter(BooleanOperator op, Filter filter1, Filter filter2) {
		this.op = op;
		this.filter1 = filter1;
		this.filter2 = filter2;
	}

	/**
	 * @return the op
	 */
	public BooleanOperator getOp() {
		return op;
	}

	/**
	 * @return the filter1
	 */
	public Filter getFilter1() {
		return filter1;
	}

	/**
	 * @return the filter2
	 */
	public Filter getFilter2() {
		return filter2;
	}

	@Override
	public boolean matches(Map<String, Tree> e,
			HashMap<String, Map<String, Tree>> worldstate)
			throws FilterException {
		if (op == null)
			return !filter1.matches(e, worldstate);

		switch (op) {
		case AND:
			return filter1.matches(e, worldstate)
					&& filter2.matches(e, worldstate);

		case OR:
			return filter1.matches(e, worldstate)
					|| filter2.matches(e, worldstate);

		case XOR:
			return filter1.matches(e, worldstate)
					^ filter2.matches(e, worldstate);

		case IMPLIES:
			return !filter1.matches(e, worldstate)
					|| filter2.matches(e, worldstate);

		case IFF:
			return filter1.matches(e, worldstate) == filter2.matches(e,
					worldstate);

		}

		throw new FilterException("Unknown operator " + op);
	}

	@Override
	public Tree[] serialize(boolean explicit, Map<Object, String> refmap,
			ClassLoader classLoader) {
		InnerNode result = new InnerNode();

		if (op == null)
			result.add(Tree.fromObject(filter1, explicit, refmap, classLoader));
		else {
			result.add(Tree.fromObject(op, explicit, refmap, classLoader));
			result.add(Tree.fromObject(filter1, explicit, refmap, classLoader));
			result.add(Tree.fromObject(filter2, explicit, refmap, classLoader));
		}

		return new Tree[] { result };
	}

	@Override
	public String getTypeIdentifier() {
		return "publishsubscribe/compound-filter";
	}

	@DeserializationMethod
	public static CompoundFilter deserialize(Tree[] exps, ClassLoader cl,
			String id, Map<String, Object> idmap)
			throws DeserializableException {
		if (exps.length != 1)
			throw new DeserializableException(null,
					"Accept only one expression");

		Tree e = exps[0];
		if (!(e instanceof InnerNode))
			throw new DeserializableException(e, "Expression must be a struct");

		InnerNode s = (InnerNode) e;
		if (s.getNumberOfSubTrees() == 1) {
			Filter f = s.getSubTree(0).getAs(Filter.class);
			return new CompoundFilter(f);
		}

		if (s.getNumberOfSubTrees() == 3) {
			BooleanOperator op = s.getSubTree(0).getAs(BooleanOperator.class);
			Filter f1 = s.getSubTree(1).getAs(Filter.class);
			Filter f2 = s.getSubTree(2).getAs(Filter.class);
			return new CompoundFilter(op, f1, f2);
		}

		throw new DeserializableException(s,
				"Struct must have 1 or 3 sub-expressions");
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null)
			return false;
		if (!(obj instanceof CompoundFilter))
			return false;

		CompoundFilter o = (CompoundFilter) obj;

		if (o.op == null) {
			if (op != null)
				return false;
			return o.filter1.equals(filter1);
		}

		return o.op.equals(op) && o.filter1.equals(filter1)
				&& o.filter2.equals(filter2);
	}

	@Override
	public int hashCode() {
		if (op == null)
			return filter1.hashCode();

		return op.hashCode() + filter1.hashCode() + filter2.hashCode();
	}

	@Override
	public String toString() {
		if (op == null)
			return "!" + filter1;

		return "(" + filter1 + " " + op + " " + filter2 + ")";
	}
}
